#include <qlib/geo/polygon.h>
#include <qlib/tools/clipper_tools.h>
#include <qlib/tools/earcut/polygon_earcut.hpp>

namespace qlib {

Polygon::Polygon() {
  layer = 0;
  datatype = 0;

  owner = nullptr;
}

Polygon::Polygon(const Polygon& oth) {
  operator=(oth);
}
Polygon& Polygon::operator=(const Polygon& other) {
  box = other.box;
  layer = other.layer;
  datatype = other.datatype;

  points = other.points;
  repetition = other.repetition;

  properties.resize(other.properties.size());
  for (size_t i = 0; i < properties.size(); i++) {
    if (other.properties[i]) {
      properties[i] = Property::create();
      *properties[i] = *other.properties[i];
    }
  }

  return *this;
}

Polygon::s_ptr Polygon::copy() const {
  auto res = Polygon::create();
  *res = *this;

  return res;
}

// 获取应用重复后产生的多边形
void Polygon::apply_repetition(PolygonPtrArray& result) {
  if (repetition.type == RepetitionType::None)
    return;

  // 获取偏移值
  Vec2dArray offsets = {};
  repetition.get_offsets(offsets);
  repetition = Repetition();  // 释放其vector占用空间

  // 跳过(0,0)
  Vec2d* offset_p = offsets.data() + 1;
  result.reserve(offsets.size() - 1);

  // 构造新的多边形，并进行偏移
  for (uint64_t offset_count = offsets.size() - 1; offset_count > 0; offset_count--) {
    result.emplace_back();
    result.back() = Polygon::create(*this);
    result.back()->translate(offset_p->x, offset_p->y);
    offset_p++;
  }
}

// 获取应用重复后产生的多边形
void Polygon::apply_repetition(GeoRegMap& result) {
  if (repetition.type == RepetitionType::None)
    return;

  // 获取偏移值
  Vec2dArray offsets = {};
  repetition.get_offsets(offsets);
  repetition = Repetition();  // 释放其vector占用空间

  // 跳过(0,0)
  Vec2d* offset_p = offsets.data() + 1;
  result.reserve(offsets.size() - 1);

  auto& reg = result[Tag{layer, datatype}];
  // 构造新的多边形，并进行偏移
  for (uint64_t offset_count = offsets.size() - 1; offset_count > 0; offset_count--) {
    auto poly = Polygon::create(*this);
    poly->translate(offset_p->x, offset_p->y);

    auto e = reg.create();
    reg.emplace<Polygon::s_ptr>(e, poly);

    offset_p++;
  }
}

// 计算面积
double Polygon::area() const {
  if (points.size() < 3)
    return 0;

  double result = 0;
  const Vec2d* p = points.data();

  Vec2d v0 = *p++;
  Vec2d v1 = *p++ - v0;

  // 叉积法计算三角形面积
  for (uint64_t num = points.size() - 2; num > 0; num--) {
    Vec2d v2 = *p++ - v0;
    result += v1.cross(v2);
    v1 = v2;
  }

  // 乘以0.5和重复次数
  if (repetition.type != RepetitionType::None)
    result *= repetition.get_count();
  return 0.5 * fabs(result);
}

double Polygon::signed_area() const {
  if (points.size() < 3)
    return 0;
  double result = 0;
  const Vec2d* p = points.data();
  Vec2d v0 = *p++;
  Vec2d v1 = *p++ - v0;
  for (uint64_t num = points.size() - 2; num > 0; num--) {
    Vec2d v2 = *p++ - v0;
    result += v1.cross(v2);
    v1 = v2;
  }
  return 0.5 * result;
}

// 计算包围盒(min,max)
Vec2dArray Polygon::bounding_box() {
  Vec2dArray res(2);
  Vec2d& min = res[0];
  Vec2d& max = res[1];

  if (points.size() == 0) {
    min = {0, 0};
    max = {0, 0};
  }

  min.x = min.y = DBL_MAX;
  max.x = max.y = -DBL_MAX;

  // 计算未应用重复下的包围盒
  const Vec2d* p = points.data();
  for (uint64_t num = points.size(); num > 0; num--, p++) {
    if (p->x < min.x)
      min.x = p->x;
    if (p->x > max.x)
      max.x = p->x;
    if (p->y < min.y)
      min.y = p->y;
    if (p->y > max.y)
      max.y = p->y;
  }

  // 考虑重复
  if (repetition.type != RepetitionType::None) {
    // 获取重复下的极值
    Vec2dArray offsets = {};
    repetition.get_extrema(offsets);

    Vec2d* off = offsets.data();
    Vec2d min0 = min;
    Vec2d max0 = max;

    // 将极值应用到每个包围盒上
    for (uint64_t i = offsets.size(); i > 0; i--, off++) {
      if (min0.x + off->x < min.x)
        min.x = min0.x + off->x;
      if (max0.x + off->x > max.x)
        max.x = max0.x + off->x;
      if (min0.y + off->y < min.y)
        min.y = min0.y + off->y;
      if (max0.y + off->y > max.y)
        max.y = max0.y + off->y;
    }
  }

  return res;
}

// 检查是否包含点
bool Polygon::contain(const Vec2d& point) const {
  if (points.size() == 0) {
    return false;
  }

  // 取出最后一个并判断是否相等
  Vec2d p0 = points.back();
  if (p0 == point) {
    return true;
  }

  int64_t winding = 0;
  const Vec2d* v = points.data();

  // 取出每一个点
  for (uint64_t i = points.size(); i > 0; i--, v++) {
    Vec2d p1 = *v;

    // 判断是否等于此点，或者y值等于p0且被二者包夹
    if (p1.y == point.y && (p1.x == point.x || (p0.y == point.y) && (p1.x > point.x) == (p0.x < point.x))) {
      return true;
    }
    if ((p0.y < point.y) != (p1.y < point.y)) {
      if (p0.x >= point.x) {
        if (p1.x > point.x) {
          winding += p1.y > p0.y ? 1 : -1;
        } else {
          double det = (p0 - point).cross(p1 - point);
          if (det == 0) {
            return true;
          }
          if ((det > 0) == (p1.y > p0.y)) {
            winding += p1.y > p0.y ? 1 : -1;
          }
        }
      } else if (p1.x > point.x) {
        double det = (p0 - point).cross(p1 - point);
        if (det == 0) {
          return true;
        }
        if ((det > 0) == (p1.y > p0.y)) {
          winding += p1.y > p0.y ? 1 : -1;
        }
      }
    }
    p0 = p1;
  }
  return winding != 0;
}
void Polygon::contain(const Vec2dArray& points, std::vector<bool>& res) {
  res.resize(points.size());

  for (size_t i = 0; i < points.size(); i++) {
    res[i] = contain(points[i]);
  }
}
bool Polygon::contain_all(const Vec2dArray& points) {
  Vec2dArray tbox = bounding_box();
  Vec2d& min = tbox[0];
  Vec2d& max = tbox[1];

  Vec2d point;
  for (uint64_t i = 0; i < points.size(); i++) {
    point = points[i];
    if (point.x < min.x || point.x > max.x || point.y < min.y || point.x > max.x)
      return false;
  }
  for (uint64_t i = 0; i < points.size(); i++) {
    if (!contain(points[i]))
      return false;
  }
  return true;
}
bool Polygon::contain_any(const Vec2dArray& points) {
  Vec2dArray tbox = bounding_box();
  Vec2d& min = tbox[0];
  Vec2d& max = tbox[1];

  for (uint64_t i = 0; i < points.size(); i++) {
    Vec2d point = points[i];
    if (point.x >= min.x && point.x <= max.x && point.y >= min.y && point.x <= max.x && contain(point))
      return true;
  }
  return false;
}

void Polygon::fillet(const std::vector<double>& radii, double tolerance) {
  if (points.size() < 3)
    return;

  Array<Vec2d> old_pts;
  old_pts = points;
  points.clear();

  const uint64_t old_size = old_pts.size();
  uint64_t j = 0;
  if (old_pts[old_size - 1] == old_pts[0]) {
    j = old_size - 1;
    while (old_pts[j - 1] == old_pts[j])
      j -= 1;
  }
  const uint64_t last = j;

  uint64_t i = j == 0 ? old_size - 1 : j - 1;
  Vec2d p0 = old_pts[i];
  Vec2d p1 = old_pts[j];
  Vec2d v0 = p1 - p0;
  double len0 = v0.normalize();

  uint64_t k = last + 1;
  while (k != last) {
    k = j == old_size - 1 ? 0 : j + 1;
    while (old_pts[k] == old_pts[j])
      k += 1;

    const Vec2d p2 = old_pts[k];
    Vec2d v1 = p2 - p1;
    const double len1 = v1.normalize();

    const double theta = acos(v0.inner(v1));
    if (theta > 1e-12) {
      const double tant = tan(0.5 * theta);
      const double cost = cos(0.5 * theta);
      Vec2d dv = v1 - v0;
      const double fac = 1 / (cost * dv.length());
      dv *= fac;

      double radius = radii[j % radii.size()];
      double max_len = radius * tant;
      if (max_len > 0.5 * (len0 - tolerance)) {
        max_len = 0.5 * (len0 - tolerance);
        radius = max_len / tant;
      }
      if (max_len > 0.5 * (len1 - tolerance)) {
        max_len = 0.5 * (len1 - tolerance);
        radius = max_len / tant;
      }

      double a0 = (v0 * -tant - dv).angle();
      double a1 = (v1 * tant - dv).angle();
      if (a1 - a0 > M_PI)
        a1 -= 2 * M_PI;
      else if (a1 - a0 < -M_PI)
        a1 += 2 * M_PI;

      uint64_t n = 1;
      if (radius > 0) {
        n = arc_num_points(fabs(a1 - a0), radius, tolerance);
        if (n == 0)
          n = 1;
      }

      vector_reserve_now(points, n);
      if (n == 1) {
        points.push_back(p1);
      } else {
        for (uint64_t l = 0; l < n; l++) {
          const double a = a0 + l * (a1 - a0) / (n - 1.0);
          Vec2d cosi = {cos(a), sin(a)};
          points.push_back(p1 + (dv + cosi) * radius);
        }
      }
    } else {
      points.push_back(p1);
    }

    // i = j;
    j = k;
    p0 = p1;
    p1 = p2;
    v0 = v1;
    len0 = len1;
  }

  old_pts.clear();
}

// 将此多边形拆分为多个
void Polygon::fracture(uint64_t max_points, double precision, PolygonPtrArray& result) const {
  if (max_points <= 4)
    return;
  Polygon::s_ptr poly = Polygon::create();
  poly->points = points;
  result.push_back(poly);

  double scaling = 1.0 / precision;
  for (uint64_t i = 0; i < result.size();) {
    Polygon::s_ptr subj = result[i];
    uint64_t num_points = subj->points.size();
    if (num_points <= max_points) {
      i++;
      continue;
    }

    Vec2dArray box = subj->bounding_box();
    Vec2d& min = box[0];
    Vec2d& max = box[1];

    const uint64_t num_cuts = num_points / max_points;
    bool x_axis;
    std::vector<double> coords(num_points);

    if (max.x - min.x > max.y - min.y) {
      x_axis = true;
      double* x = coords.data();
      Vec2d* pt = subj->points.data();
      for (uint64_t j = 0; j < num_points; j++)
        (*x++) = (pt++)->x;
    } else {
      x_axis = false;
      double* y = coords.data();
      Vec2d* pt = subj->points.data();
      for (uint64_t j = 0; j < num_points; j++)
        (*y++) = (pt++)->y;
    }
    std::sort(coords.begin(), coords.end());

    Array<double> interior_coords;
    size_t start = 0;
    size_t end = coords.size() - 1;

    while (coords.front() == coords[start])
      ++start;
    while (coords.back() == coords[end])
      --end;

    interior_coords.reserve(end + 1);
    for (size_t i = start; i <= end; i++) {
      interior_coords.push_back(coords[i]);
    }

    Array<double> cuts = {};
    if (interior_coords.size() == 0) {
      cuts.push_back((coords[0] + coords[num_points - 1]) * 0.5);
    } else if (interior_coords.size() <= num_cuts) {
      cuts.insert(cuts.end(), interior_coords.begin(), interior_coords.end());
    } else {
      vector_reserve_now(cuts, num_cuts);
      const double frac = interior_coords.size() / (num_cuts + 1.0);
      for (uint64_t j = 1; j <= num_cuts; j++)
        cuts.push_back(interior_coords[(uint64_t)(j * frac)]);
    }
    coords.clear();

    std::vector<PolygonPtrArray> chopped(cuts.size() + 1);
    ClipperTool::slice(subj, cuts, x_axis, scaling, chopped);

    subj->points.clear();

    array_remove_unordered(result, i);

    uint64_t total = 0;
    for (uint64_t j = 0; j <= cuts.size(); j++)
      total += chopped[j].size();
    vector_reserve_now(result, total);

    for (uint64_t j = 0; j <= cuts.size(); j++) {
      result.insert(result.end(), chopped[j].begin(), chopped[j].end());
      chopped[j].clear();
    }

    cuts.clear();
    vector_free_space(chopped);
  }

  for (uint64_t i = 0; i < result.size(); i++) {
    poly = result[i];
    poly->layer = layer;
    poly->datatype = datatype;
    poly->repetition = repetition;
    poly->properties = properties;
  }
}

// 镜像
void Polygon::mirror(Vec2d p0, Vec2d p1) {
  Vec2d v = p1 - p0;
  double tmp = v.length_sq();
  if (tmp == 0)
    return;

  Vec2d r = v * (2 / tmp);
  Vec2d p2 = p0 * 2;

  Vec2d* p = points.data();
  for (uint64_t num = points.size(); num > 0; num--, p++)
    *p = v * (*p - p0).inner(r) - *p + p2;
}

// 计算周长
double Polygon::perimeter() const {
  if (points.size() < 3)
    return 0;
  double result = 0;
  const Vec2d* p = points.data();
  Vec2d v0 = *p++;
  Vec2d v1;

  // 依次计算每个点与上一个点间的距离
  for (uint64_t num = points.size() - 1; num > 0; num--) {
    v1 = *p++ - v0;
    result += v1.length();
    v0 += v1;
  }
  result += (points[0] - points.back()).length();
  if (repetition.type != RepetitionType::None)
    result *= repetition.get_count();
  return result;
}

// 缩放
void Polygon::scale(double sx, double sy, Vec2d center) {
  Vec2d scale(sx, sy);
  for (size_t i = 0; i < points.size(); i++) {
    // 平移到原点、缩放、平移回去
    points[i] = (points[i] - center) * scale + center;
  }
}
// 旋转
void Polygon::rotate(double angle, Vec2d center) {
  double ca = cos(angle);
  double sa = sin(angle);

  Vec2d vt;
  for (size_t i = 0; i < points.size(); i++) {
    // 平移到原点、矩阵乘法旋转、平移回去
    vt = points[i] - center;
    points[i].x = vt.x * ca - vt.y * sa + center.x;
    points[i].y = vt.x * sa + vt.y * ca + center.y;
  }
}
// 平移
void Polygon::translate(double dx, double dy) {
  Vec2d offset(dx, dy);
  for (size_t i = 0; i < points.size(); i++) {
    points[i] += offset;
  }
}

// 变换
void Polygon::transform(double magnification, bool x_reflection, double rotation, Vec2d origin) {

  double ca = cos(rotation);
  double sa = sin(rotation);

  Vec2d* p = points.data();
  for (uint64_t num = points.size(); num > 0; num--, p++) {
    Vec2 q = *p * magnification;
    if (x_reflection)
      q.y = -q.y;
    p->x = q.x * ca - q.y * sa + origin.x;
    p->y = q.x * sa + q.y * ca + origin.y;
  }
}

Vec2dArray Polygon::render_aabb() {
  return bounding_box();
}

Vec2dArray Polygon::render_poly() {
  return points;
}

Vec2dArray Polygon::render_absorp() {
  return points;
}

Vec2d Polygon::get_center_pos() {
  auto box = bounding_box();
  return (box[0] + box[1]) / 2.0;
}

void Polygon::set_center_pos(Vec2d center) {
  auto box = bounding_box();
  auto center_pos = (box[0] + box[1]) / 2.0;

  auto trans = center - center_pos;
  translate(trans.x, trans.y);
}

// // 获取所有边
// std::vector<Vec2dArray> Polygon::get_edges() {
//   std::vector<Vec2dArray> edges = {};
//   for (uint32_t i = 0; i < points.size() - 1; i++) {
//     edges.push_back(Vec2dArray{points[i], points[i + 1]});
//   }
//   edges.push_back(Vec2dArray{points[points.size() - 1], points[0]});
//   return edges;
// }

// // 获取边中心点
// Vec2dArray Polygon::get_edges_mid() {
//   auto edges = get_edges();
//   Vec2dArray res = {};
//   for (auto& edge : edges) {
//     auto c = (edge[0] + edge[1]) / 2.0f;
//     res.push_back(c);
//   }
//   return res;
// }

uint32_t Polygon::getLayer() {
  return layer;
}
void Polygon::setLayer(uint32_t _layer) {
  layer = _layer;
}

PolygonParts Polygon::get_parts() {
  auto v_points = connect_end(points);
  auto v_edges = get_edges(v_points);
  auto v_edges_mid = get_edges_mid(v_edges);

  auto box = bounding_box();
  auto box_points = box_to_points(box);
  auto box_edges = get_edges(box_points);
  auto box_edges_mid = get_edges_mid(box_edges);

  auto center = box_center(box);

  auto res = std::make_tuple(center, v_points, v_edges, v_edges_mid, box_points, box_edges, box_edges_mid);
  return res;
}

Vec2dArray Polygon::query_parts(GeoPartType type, uint32_t id) {
  auto parts = get_parts();
  if (type == GeoPartType::Center)
    return Vec2dArray{std::get<0>(parts)};
  if (type == GeoPartType::Point)
    return Vec2dArray{std::get<1>(parts)[id]};
  if (type == GeoPartType::Edge)
    return std::get<2>(parts)[id];
  if (type == GeoPartType::EdgeMid)
    return Vec2dArray{std::get<3>(parts)[id]};
  if (type == GeoPartType::BoxPoint)
    return Vec2dArray{std::get<4>(parts)[id]};
  if (type == GeoPartType::BoxEdge)
    return std::get<5>(parts)[id];
  if (type == GeoPartType::BoxEdgeMid)
    return Vec2dArray{std::get<6>(parts)[id]};
  return Vec2dArray{};
}

void Polygon::genTriIndices() {
  // if (points.size() > 40) {
  //   PolygonPtrArray polys;
  //   fracture(40, 1e-4, polys);
  //   tri_indices.clear();
  //   for (auto& poly : polys) {
  //     auto indices = tools::tri_earcut(poly->points);
  //     tri_indices.insert(tri_indices.end(), indices.begin(), indices.end());
  //   }
  // } else {
  tri_indices = tools::tri_earcut(points);
  // }
}

}  // namespace qlib